/************************************************************************

 This code forms the base of the operating system you will
 build.  It has only the barest rudiments of what you will
 eventually construct; yet it contains the interfaces that
 allow test.c and z502.c to be successfully built together.

 Revision History:
 1.0 August 1990
 1.1 December 1990: Portability attempted.
 1.3 July     1992: More Portability enhancements.
 Add call to sample_code.
 1.4 December 1992: Limit (temporarily) printout in
 interrupt handler.  More portability.
 2.0 January  2000: A number of small changes.
 2.1 May      2001: Bug fixes and clear STAT_VECTOR
 2.2 July     2002: Make code appropriate for undergrads.
 Default program start is in test0.
 3.0 August   2004: Modified to support memory mapped IO
 3.1 August   2004: hardware interrupt runs on separate thread
 3.11 August  2004: Support for OS level locking
 ************************************************************************/
#include <queue>
#include             "global.h"
#include             "syscalls.h"
#include             "protos.h"
#include             "string.h"
#include <string>
#include "Definitions.h"
using namespace std;

extern char MEMORY[];
//extern BOOL          POP_THE_STACK;
extern UINT16 *Z502_PAGE_TBL_ADDR;
extern INT16 Z502_PAGE_TBL_LENGTH;
extern INT16 Z502_PROGRAM_COUNTER;
extern INT16 Z502_INTERRUPT_MASK;
extern INT32 SYS_CALL_CALL_TYPE;
extern INT16 Z502_MODE;
extern Z502_ARG Z502_ARG1;
extern Z502_ARG Z502_ARG2;
extern Z502_ARG Z502_ARG3;
extern Z502_ARG Z502_ARG4;
extern Z502_ARG Z502_ARG5;
extern Z502_ARG Z502_ARG6;

extern void *TO_VECTOR[];
extern INT32 CALLING_ARGC;
extern char **CALLING_ARGV;
PCB *pidList = NULL;
PCB *timerList = NULL;
EVENT_t	*eventList = NULL;
PCB *current_PCB = NULL;
INT32 inc_pid = 0;
INT32 total_pid = 0;
INT32 event_count = 0;
INT32  PRINT_COUNT = 0;
INT32 inc_event = 0;

char *call_names[] = { "mem_read ", "mem_write", "read_mod ", "get_time ",
		"sleep    ", "get_pid  ", "create   ", "term_proc", "suspend  ",
		"resume   ", "ch_prior ", "send     ", "receive  ", "disk_read",
		"disk_wrt ", "def_sh_ar" };

//vector<myprocess*> processQueue;
/************************************************************************
 INTERRUPT_HANDLER
 When the Z502 gets a hardware interrupt, it transfers control to
 this routine in the OS.
 ************************************************************************/
void    interrupt_handler( void ) {
		INT32			device_id;
        INT32			Status;
        INT32			Index = 0;
        INT32			currentTime;
        INT32			success;
        PCB			*switchPCB;
		INT32			wokenUp;
		INT32			sleeptime;

        // Get cause of interrupt
        ZCALL( MEM_READ(Z502InterruptDevice, &device_id ) );
        //Loop interrupts until there are no more, add all to the event Queue
        while(device_id != -1){
            // Set this device as target of our query
            ZCALL( MEM_WRITE(Z502InterruptDevice, &device_id ) );
            // Now read the status of this device
            ZCALL( MEM_READ(Z502InterruptStatus, &Status ) );

            CALL( add_to_eventQueue(&device_id, &Status) );
            ZCALL( MEM_WRITE(Z502InterruptClear, &Index ) );

            ZCALL( MEM_READ(Z502InterruptDevice, &device_id ) );
        }
    return;
}                                       /* End of interrupt_handler */

/************************************************************************
 FAULT_HANDLER
 The beginning of the OS502.  Used to receive hardware faults.
 ************************************************************************/

void fault_handler(void) {
	INT32           device_id;
    INT32           status;
    INT32           Index = 0;
    INT32           frame = -1;
    INT16           call_type;
    char            DATA[PGSIZE];

    call_type = (INT16)SYS_CALL_CALL_TYPE;

    // Get cause of interrupt
    MEM_READ(Z502InterruptDevice, &device_id );
    // Set this device as target of our query
    MEM_WRITE(Z502InterruptDevice, &device_id );
    // Now read the status of this device
    MEM_READ(Z502InterruptStatus, &status );


    //Switch case on fault device ID.
    //Terminate Process and children on CPU and privledge instruction
    switch(device_id){
        case(CPU_ERROR):

            terminate_Process(-2, &Index);
            MEM_WRITE(Z502InterruptClear, &Index );
            break;

        case(PRIVILEGED_INSTRUCTION):

            terminate_Process(-2, &Index);
            MEM_WRITE(Z502InterruptClear, &Index );
            break;

        case(INVALID_MEMORY):



            //Setup address with frame and valid bit
            Z502_PAGE_TBL_ADDR[status] = frame;
            Z502_PAGE_TBL_ADDR[status] |= PTBL_VALID_BIT;

/*            printf("VALID %d\t", (*Z502_PAGE_TBL_ADDR & 0x8000) >> 15);
            printf("MOD %d\t", (*Z502_PAGE_TBL_ADDR & 0x4000) >> 14);
            printf("REF %d\t", (*Z502_PAGE_TBL_ADDR & 0x2000) >> 13);

            printf("SUM %d\n", (*Z502_PAGE_TBL_ADDR & 0xE000) >> 13);*/

            //Based on Call Type
            //MEMREAD or MEMWRITE
           if( call_type == SYSNUM_MEM_READ ){
                ZCALL( MEM_READ( (INT32) Z502_ARG1.VAL, (INT32 *)Z502_ARG2.PTR ) );
            }
            else if( call_type == SYSNUM_MEM_WRITE ){
                ZCALL( MEM_WRITE( (INT32) Z502_ARG1.VAL, (INT32 *)Z502_ARG2.PTR ) );
            }
            //Print memory that is being used.

            break;
    }

    // Clear out this device - we're done with it
    MEM_WRITE(Z502InterruptClear, &Index );
}
/************************************************************************
 SVC
 The beginning of the OS502.  Used to receive software interrupts.
 All system calls come to this point in the code and are to be
 handled by the student written code here.
 ************************************************************************/
void   OS_Create_Process( char * name, void * procPTR,
        INT32 priority, INT32 *pid, INT32 *error, INT32 SWITCH){

    //Check for illegal priority
    if (priority < 0){
        //debugPrint("OS CREATE ERROR: ILLEGAL PRIORITY");
        (*error) = ERR_BAD_PARAM;
        //return -1;
    }
    //Check if input name is a duplicate
    if (pidList != NULL){
        if(check_name(&pidList, name) == 0){
            //debugPrint("OS CREATE ERROR: DUPLICATE NAME");
            (*error) = ERR_BAD_PARAM;
          //  return -1;
        }
    }
    //Check if MAX_PIDs (100) exceeded
    if (total_pid >= MAX_PIDs){
       // debugPrint("OS CREATE ERROR: EXCEEDED MAX NUMBER OF PIDs");
        (*error) = ERR_BAD_PARAM;
        //return -1;
    }
    inc_pid++;

    //Checks are clear, make space and build PCB
    PCB *curr = (PCB *)(malloc(sizeof(PCB)));
    curr->state = NEW_STATE;
    curr->processId = inc_pid;
    curr->time = 0;
    memset(curr->processName, 0, MAX_NAME+1);
    strcpy(curr->processName,name);
    curr->pri = priority;
    curr->next = NULL;
    curr->prev = NULL;
    curr->msg_state = READY_MSG;
    curr->disk = -1;
    curr->msg_count = 0;

    //Set all values in pageTable to zero
    memset(curr->pageTable, 0, VIRTUAL_MEM_PGS+1);

    //If there is a process currently running (not first PCB)
    //Set the new PCB's parent to the running process
    if (current_PCB != NULL) curr->parent = current_PCB->processId;

    //Add to Ready List
    CALL( add_to_readyQueue(&pidList, curr) );

    //Return Success and the ID to the OS
    (*error) = ERR_SUCCESS;
    (*pid) = curr->processId;

    //STATE PRINT OUT
//    if( CREATEpo && DEBUGFLAG){
//        SP_setup( SP_NEW_MODE, inc_pid );
//        SP_setup( SP_PRIORITY_MODE, priority );
//        SP_print_header();
//        SP_print_line();
//        printReady();
//    }
//
    //SWITCH if first PCB, else make the context and return
    if (SWITCH == 1) CALL( make_switch_Savecontext(curr, procPTR) );
    if (SWITCH != 1) CALL( make_context(curr, procPTR) );

   // return 0;
}                                       /* End off OS_Create_Process */
INT32 check_name(PCB **ptrFirst, char *name) {
	lockReady();
	PCB *curr = *ptrFirst;
	while (curr != NULL) {
		if (strcmp(curr->processName, name) == 0) {
			unlockReady();
			return 0;
		}
		curr = (PCB*) curr->next;
	}

	unlockReady();
	return 1;
}
void get_currentTime(INT32* myTime) {
	//lock hardware
	INT32 LockResult;
	ZCALL( Z502_READ_MODIFY( MEMORY_INTERLOCK_BASE + 2, 1, true, &LockResult ));
	INT32 tim2;
	MEM_READ( Z502ClockStatus, &tim2);

	(*myTime) = tim2;
//unlock hardware
	ZCALL( Z502_READ_MODIFY( MEMORY_INTERLOCK_BASE + 2, 0, true, &LockResult ));

}
void checkTimer ( INT32 currentTime , INT32 *time ){
	 INT32 LockResult;
	ZCALL(Z502_READ_MODIFY( MEMORY_INTERLOCK_BASE, 1, true, &LockResult ));

	PCB *ptrCheck = timerList;
	if (ptrCheck == NULL){
		 INT32 LockResult;
		 ZCALL(Z502_READ_MODIFY( MEMORY_INTERLOCK_BASE, 0, true, &LockResult ));
		*time=-1;
	}
	// INT32 LockResult;
	 ZCALL(Z502_READ_MODIFY( MEMORY_INTERLOCK_BASE, 0, true, &LockResult ));
	*time= (ptrCheck->time- currentTime);
}
void Start_Timer(INT32 Time) {
    INT32        Status;
    ZCALL (INT32 LockResult;
	Z502_READ_MODIFY( MEMORY_INTERLOCK_BASE + 2, 1, true, &LockResult );
);
    ZCALL( MEM_WRITE( Z502TimerStart, &Time ) );
    ZCALL( MEM_READ( Z502TimerStatus, &Status ) );
    ZCALL ( INT32 LockResult;
	Z502_READ_MODIFY( MEMORY_INTERLOCK_BASE + 2, 0, true, &LockResult );
 );
}

void eventHandler ( void ) {
    EVENT_t 		*ptrCheck = eventList;

    INT32			interrupt;
    INT32			eventID;
    INT32			currentTime;
    INT32			timeUp = 0;
    INT32			diskUp = 0;
    INT32			sleeptime;

    //Remove all Events from Queue
    while (ptrCheck != NULL){
        interrupt = ptrCheck->device_ID;
        eventID = ptrCheck->id;
        CALL( rm_from_eventQueue(eventID) );
        switch(interrupt){
            //TIMER INTERRUPT
            case(TIMER_INTERRUPT):
                //Get current CPU Time
                CALL(  get_currentTime( &currentTime) );
                //Wake up all WAITING items that are before currentTime
                CALL( wake_timerList(currentTime,&timeUp) );
                //Get sleeptime
                CALL(  checkTimer (currentTime,&sleeptime) );
                //There are more items on timerQueue
                //Start the new timer
                if( sleeptime > 0) CALL( Start_Timer(sleeptime) );
                break;
            //DISK INTERRUPT
//            case(DISK_INTERRUPT_DISK1):
//            case(DISK_INTERRUPT_DISK2):
//            case(DISK_INTERRUPT_DISK3):
//            case(DISK_INTERRUPT_DISK4):
//            case(DISK_INTERRUPT_DISK5):
//            case(DISK_INTERRUPT_DISK6):
//            case(DISK_INTERRUPT_DISK7):
//            case(DISK_INTERRUPT_DISK8):
//            case(DISK_INTERRUPT_DISK9):
//            case(DISK_INTERRUPT_DISK10):
//            case(DISK_INTERRUPT_DISK11):
//            case(DISK_INTERRUPT_DISK12):
//                //Call disk Handler for all disk interrupts
//                CALL( diskUp = diskHandler(ptrCheck->Status, interrupt - 4) );
//                diskUp += diskUp;
//        break;
        }
        ptrCheck = (EVENT_t*)ptrCheck->next;
    }
    //If any items were wokenUp from timer or disks, check for context switch
    if( diskUp > 0 || timeUp > 0){
        switch_Context();
    }

}
// Helper function, that is used to IDLE
// If there are no events, Z502 IDLE is called,
// otherwise the EVENT HANDLER is called
void EVENT_IDLE ( void ) {
    if (event_count == 0) CALL( Z502_IDLE() );
    CALL( eventHandler() );
}

void switch_Context ( void ){
    PCB			*switchPCB;

    if (total_pid <= 0) ZCALL( Z502_HALT() );
    CALL( switchPCB = get_readyPCB() );
     //No ready items, IDLE
    if (switchPCB == NULL){
        ZCALL( EVENT_IDLE() );
    }
     //Switch if current PCB is not ready PCB
    else if ( switchPCB->processId != current_PCB->processId ){
        CALL( switch_Savecontext( switchPCB ) );
    }
    //Return if they are the same
    else if ( switchPCB->processId == current_PCB->processId ){
        return;
    }
     //Otherwise, IDLE
    else{
        ZCALL( EVENT_IDLE() );
    }
}


void svc(void) {

	INT16 call_type;
	static INT16 do_print = 10;

	call_type = (INT16) SYS_CALL_CALL_TYPE;
	if (do_print > 0) {
		printf("SVC handler: %s %8ld %8ld %8ld %8ld %8ld %8ld\n",
				call_names[call_type], Z502_ARG1.VAL, Z502_ARG2.VAL,
				Z502_ARG3.VAL, Z502_ARG4.VAL, Z502_ARG5.VAL, Z502_ARG6.VAL);
		do_print--;
	}

	 if ( (event_count < 0) && (call_type != SYSNUM_TERMINATE_PROCESS) ){
	        CALL( eventHandler() );
	    }

	switch (call_type) {
	case SYSNUM_GET_TIME_OF_DAY: {

		INT32 myTime;
		get_currentTime(&myTime);
		*(INT32*) Z502_ARG1.PTR = myTime;

	}
		break;
	case SYSNUM_TERMINATE_PROCESS: {

		CALL(terminate_Process((INT32)Z502_ARG1.VAL, (INT32*)Z502_ARG2.PTR ));
	}
		break;

	case SYSNUM_CREATE_PROCESS: {
		CALL(
				OS_Create_Process((char*)Z502_ARG1.PTR, (void *)Z502_ARG2.PTR, (INT32)Z502_ARG3.VAL,(INT32*)Z502_ARG4.PTR, (INT32*)Z502_ARG5.PTR, 0));
	}
		break;
	case SYSNUM_GET_PROCESS_ID: {
		CALL(
				get_PCB_ID(&pidList, (char *)Z502_ARG1.PTR, (INT32 *)Z502_ARG2.PTR, (INT32 *)Z502_ARG3.PTR));
	}
		break;
	case SYSNUM_SUSPEND_PROCESS: {
		CALL(suspend_Process(Z502_ARG1.VAL,(INT32*)Z502_ARG2.PTR));
	}
		break;
	case SYSNUM_RESUME_PROCESS: {
		CALL(resume_Process(Z502_ARG1.VAL,(INT32*)Z502_ARG2.PTR));
	}
		break;
	case SYSNUM_SLEEP:{
		//set the sleep time to current process and move it to timer queue
		INT32 sleep,currentTime,time;
		PCB* next;
		CALL( sleep = Z502_ARG1.VAL );
	    CALL(  get_currentTime(&currentTime) );
	    CALL( current_PCB->time = ( sleep + currentTime ) );
	    CALL( readyQueue_to_timerQueue( current_PCB->processId  ) );
        //set the Interrupt for the next end of sleep time
	    CALL( checkTimer(currentTime , &time) );
	    if (time > 0) CALL( Start_Timer( time ) );
	    CALL( next = get_readyPCB() );
	    if (next == NULL)
	    {
	    	ZCALL(EVENT_IDLE());
	    }
	    else
	    {
	    	CALL( switch_Savecontext(next) );
	    }
	}
	break;
	   case SYSNUM_CHANGE_PRIORITY:
	            CALL( change_Priority((INT32)Z502_ARG1.VAL,(INT32)Z502_ARG2.VAL,
	                (INT32*)Z502_ARG3.PTR));
	            break;
	 case SYSNUM_SEND_MESSAGE:
	            CALL( send_Message((INT32)Z502_ARG1.VAL,(char *)Z502_ARG2.PTR,
	                (INT32)Z502_ARG3.VAL,(INT32 *)Z502_ARG4.PTR) );
	            break;
	        //Receive Message
	        case SYSNUM_RECEIVE_MESSAGE:
	            CALL( receive_Message((INT32)Z502_ARG1.VAL,(char *)Z502_ARG2.PTR,
	                (INT32)Z502_ARG3.VAL,(INT32 *)Z502_ARG4.PTR,(INT32 *)Z502_ARG5.PTR,
	                (INT32 *)Z502_ARG6.PTR) );
	            break;
	}

} // End of svc

/************************************************************************
 OS_SWITCH_CONTEXT_COMPLETE
 The hardware, after completing a process switch, calls this routine
 to see if the OS wants to do anything before starting the user
 process.
 ************************************************************************/

void    os_switch_context_complete( void )
    {
    static INT16        do_print = FALSE;
    INT16               call_type;
    MSG*              Message;

    PRINT_COUNT++;

    //Set the page table address to the current PCB's table
    Z502_PAGE_TBL_ADDR = current_PCB->pageTable;
    Z502_PAGE_TBL_LENGTH = VIRTUAL_MEM_PGS;

    call_type = (INT16)SYS_CALL_CALL_TYPE;
    if ( do_print == TRUE )
    {
        printf( "os_switch_context_complete  called before user code.\n");
        do_print = FALSE;
    }

    //If receive message called, get current PCB message from inbox
    //Return to the OS for check
    if (call_type == SYSNUM_RECEIVE_MESSAGE){
        //Z502_ARG2.PTR = *message
        //Z502_ARG4.PTR = *Length
        //Z502_ARG5.PTR = *send_ID
        CALL( get_msg_Inbox((char *)Z502_ARG2.PTR,(INT32 *)Z502_ARG4.PTR,
            (INT32 *)Z502_ARG5.PTR) );
   }
}                               /* End of os_switch_context_complete */

/************************************************************************
 OS_INIT
 This is the first routine called after the simulation begins.  This
 is equivalent to boot code.  All the initial OS components can be
 defined and initialized here.
 ************************************************************************/

void os_init(void) {

	void *next_context;
	INT32 i;

	/* Demonstrates how calling arguments are passed thru to here       */

	printf("Program called with %d arguments:", CALLING_ARGC);
	for (i = 0; i < CALLING_ARGC; i++)
		printf(" %sz", CALLING_ARGV[i]);
	printf("\n");
	printf("Calling with argument 'sample' executes the sample program.\n");

	/*          Setup so handlers will come to code in base.c           */

	TO_VECTOR[TO_VECTOR_INT_HANDLER_ADDR] = (void *) interrupt_handler;
	TO_VECTOR[TO_VECTOR_FAULT_HANDLER_ADDR] = (void *) fault_handler;
	TO_VECTOR[TO_VECTOR_TRAP_HANDLER_ADDR] = (void *) svc;

	/*  Determine if the switch was set, and if so go to demo routine.  */

	if ((CALLING_ARGC > 1) && (strcmp(CALLING_ARGV[1], "sample") == 0)) {
		ZCALL(
				Z502_MAKE_CONTEXT( &next_context, (void *)sample_code, KERNEL_MODE ));
		ZCALL( Z502_SWITCH_CONTEXT( SWITCH_CONTEXT_KILL_MODE, &next_context ));
	} /* This routine should never return!!           */

	/*  This should be done by a "os_make_process" routine, so that
	 test0 runs on a process recognized by the operating system.    */

//	ZCALL( Z502_MAKE_CONTEXT( &next_context, (void *)test1c, USER_MODE ));
//	ZCALL( Z502_SWITCH_CONTEXT( SWITCH_CONTEXT_KILL_MODE, &next_context ));

	void *proc = (void *) test1c;
	CALL(OS_Create_Process("test1c",proc, 0, &i, &i, 1));

} /* End of os_init       */
